package edu.utexas.ipc;

import java.util.StringTokenizer;

class LamportMutex implements Mutex, MessageListener  {

	private final int INFINITY = -1;
	private final int myID;
	private final int N;
	private Connection connection;
	private DirectClock clock;
    private int[] q; // request queue
    boolean[] requestSent;
    
    LamportMutex(Connection connection, Clock clock, int myID, int processCount) {
    	this.N = processCount;
    	this.myID = myID;
    	this.connection = connection;
    	this.clock = (DirectClock)clock; // requires direct dependency clock
    	this.q = new int[processCount];
    	this.requestSent = new boolean[processCount];
    	
        for (int j = 0; j < N; j++) {
            q[j] = INFINITY;
        }
        
    	// register to receive events
    	connection.registerListener(this);
    }
    
    synchronized void requestCS() {
    	
    	clock.tick();
    	q[myID] = clock.getValue(myID);
    	
        for (int id = 0; id < q.length; id++) {
        	requestSent[id] = false;
    		if (connection.isConnected(id) && id != myID) {
    			connection.send(id, "request " + q[myID]);
    			requestSent[id] = true;
    		}
    	}
   
        while (!okayCS()) {
        	myWait();
        }
    }
    
    synchronized void releaseCS() {
    	q[myID] = INFINITY;
        connection.send("release " + clock.getValue(myID));
    }
    
    boolean okayCS() {
    	
        for (int j = 0; j < N; j++) {
   	
        	// skip check if process j is off-line
        	if (j == myID || !connection.isConnected(j)) {
        		continue;
        	}

        	if (isGreater(q[myID], myID, q[j], j)) {
        		return false;
        	}
            if (isGreater(q[myID], myID, clock.getValue(j), j)) {
        		return false;
            }
        }
        return true;
    }
    
    boolean isGreater(int entry1, int pid1, int entry2, int pid2) {
        if (entry2 == INFINITY) return false;
        return ((entry1 > entry2)
                || ((entry1 == entry2) && (pid1 > pid2)));
    }
    
    public synchronized void messageReceived(Message msg) {
    	StringTokenizer sb = new StringTokenizer(msg.appData);
    	final int srcID = msg.sourceID;
    	final String msgType = sb.nextToken();
        final int timeStamp = Integer.parseInt(sb.nextToken());
        
        // if we are requesting or in the critical section and
        // a request has not been sent to this process, send the
        // mutex request now.
        if (q[myID] != INFINITY && !requestSent[srcID]) {
        	connection.send(srcID, "request " + q[myID]);
			requestSent[srcID] = true;
        }
        
        if (msgType.equals("request")) {
            q[srcID] = timeStamp;
            connection.send(srcID, "ack " + clock.getValue(myID));
        } else if (msgType.equals("release")) {
            q[srcID] = INFINITY;
        }
        notify(); // okayCS() may be true now
    }
    
	public void acquire() {
		requestCS();
	}

	public void release() {
		releaseCS();
	}

	public void close() {
		connection.close();
	}

	public synchronized void connectionLost(int processID) {
		//System.out.println("connection lost " + processID);
    	requestSent[processID] = false;
    	q[processID] = INFINITY;
    	notify(); // okayCS() may be true now
	}

	private void myWait() {
		try {
			wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/*
	private void waitWithTimeout(int timeout) throws TimeoutException, InterruptedException {
		long startTime = System.currentTimeMillis();
		wait(timeout);
		long endTime = System.currentTimeMillis();
		if ((endTime - startTime) > timeout) {
			throw new TimeoutException();
		}
	}
	*/
}
